| 1 | 1 |  | import * as ASN1 from 'asn1js' | 
            
                                                                                                            
                            
            
                                    
            
            
                | 2 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 3 | 1 |  | import { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 4 |  |  |   CONTENT_ID, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 5 |  |  |   FIELD_TYPE_ID, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 6 |  |  |   FIELD_VALUE_ID, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 7 |  |  |   IN_APP, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 8 |  |  |   RECEIPT_FIELDS_MAP, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 9 |  |  |   ReceiptFieldsKeyNames, ReceiptFieldsKeyValues, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 10 |  |  | } from './constants' | 
            
                                                                                                            
                            
            
                                    
            
            
                | 11 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 12 | 1 |  | import { ReceiptVerifier } from './ReceiptVerifier' | 
            
                                                                                                            
                            
            
                                    
            
            
                | 13 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 14 |  |  | export type Environment = 'Production' | 'ProductionSandbox' | string | 
            
                                                                                                            
                            
            
                                    
            
            
                | 15 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 16 |  |  | export type ParsedReceipt = Partial<Record<ReceiptFieldsKeyNames, string>> & { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 17 |  |  |   ENVIRONMENT: Environment | 
            
                                                                                                            
                            
            
                                    
            
            
                | 18 |  |  |   IN_APP_ORIGINAL_TRANSACTION_IDS: string[] | 
            
                                                                                                            
                            
            
                                    
            
            
                | 19 |  |  |   IN_APP_TRANSACTION_IDS: string[] | 
            
                                                                                                            
                            
            
                                    
            
            
                | 20 |  |  | } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 21 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 22 |  |  | class ReceiptParser { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 23 |  |  |   private readonly parsed: ParsedReceipt | 
            
                                                                                                            
                            
            
                                    
            
            
                | 24 |  |  |   private readonly receiptVerifier: ReceiptVerifier | 
            
                                                                                                            
                            
            
                                    
            
            
                | 25 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 26 |  |  |   constructor() { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 27 | 5 |  |     this.receiptVerifier = new ReceiptVerifier() | 
            
                                                                                                            
                            
            
                                    
            
            
                | 28 | 5 |  |     this.parsed = this.createInitialParsedReceipt() | 
            
                                                                                                            
                            
            
                                    
            
            
                | 29 |  |  |   } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 30 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 31 |  |  |   public parseReceipt(receipt: string): ParsedReceipt { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 32 | 5 |  |     if (receipt.trim() === '') { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 33 | 1 |  |       throw new Error('Receipt must be a non-empty string.') | 
            
                                                                                                            
                            
            
                                    
            
            
                | 34 |  |  |     } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 35 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 36 | 4 |  |     const rootSchemaVerification = this.receiptVerifier.verifyReceiptSchema(receipt) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 37 | 3 |  |     const content = rootSchemaVerification.result[CONTENT_ID] as ASN1.OctetString | 
            
                                                                                                            
                            
            
                                    
            
            
                | 38 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 39 | 3 |  |     this.parseReceiptContent(content) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 40 | 3 |  |     this.validateParsedFields() | 
            
                                                                                                            
                            
            
                                    
            
            
                | 41 | 3 |  |     this.deduplicateArrayFields() | 
            
                                                                                                            
                            
            
                                    
            
            
                | 42 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 43 | 3 |  |     return this.parsed | 
            
                                                                                                            
                                                                
            
                                    
            
            
                | 44 |  |  |   } | 
            
                                                                        
                            
            
                                    
            
            
                | 45 |  |  |  | 
            
                                                                        
                            
            
                                    
            
            
                | 46 |  |  |   private createInitialParsedReceipt(): ParsedReceipt { | 
            
                                                                        
                            
            
                                    
            
            
                | 47 | 5 |  |     return { | 
            
                                                                        
                            
            
                                    
            
            
                | 48 |  |  |       ENVIRONMENT: 'Production', | 
            
                                                                        
                            
            
                                    
            
            
                | 49 |  |  |       IN_APP_ORIGINAL_TRANSACTION_IDS: [], | 
            
                                                                        
                            
            
                                    
            
            
                | 50 |  |  |       IN_APP_TRANSACTION_IDS: [], | 
            
                                                                                                            
                            
            
                                    
            
            
                | 51 |  |  |     } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 52 |  |  |   } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 53 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 54 |  |  |   private parseReceiptContent(content: ASN1.OctetString): void { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 55 | 18 |  |     const sequences = this.extractSequencesFromContent(content) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 56 | 18 |  |     sequences.forEach(this.processSequence.bind(this)) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 57 |  |  |   } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 58 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 59 |  |  |   private extractSequencesFromContent(content: ASN1.OctetString): ASN1.Sequence[] { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 60 | 18 |  |     const [contentSet] = content.valueBlock.value as ASN1.Set[] | 
            
                                                                                                            
                            
            
                                    
            
            
                | 61 | 18 |  |     return contentSet.valueBlock.value | 
            
                                                                                                            
                            
            
                                    
            
            
                | 62 | 348 |  |       .filter(v => v instanceof ASN1.Sequence) as ASN1.Sequence[] | 
            
                                                                                                            
                            
            
                                    
            
            
                | 63 |  |  |   } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 64 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 65 |  |  |   private processSequence(sequence: ASN1.Sequence): void { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 66 | 348 |  |     const verifiedSequence = this.receiptVerifier.verifyFieldSchema(sequence) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 67 | 348 |  |     if (verifiedSequence) { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 68 | 348 |  |       this.handleVerifiedSequence(verifiedSequence) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 69 |  |  |     } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 70 |  |  |   } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 71 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 72 |  |  |   private handleVerifiedSequence(verifiedSequence: ASN1.CompareSchemaSuccess): void { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 73 | 348 |  |     const fieldKey = (verifiedSequence.result[FIELD_TYPE_ID] as ASN1.Integer).valueBlock.valueDec | 
            
                                                                                                            
                            
            
                                    
            
            
                | 74 | 348 |  |     const fieldValue = verifiedSequence.result[FIELD_VALUE_ID] as ASN1.OctetString | 
            
                                                                                                            
                            
            
                                    
            
            
                | 75 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 76 | 348 |  |     const handler = this.getFieldHandler(fieldKey) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 77 | 348 |  |     handler(fieldValue) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 78 |  |  |   } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 79 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 80 |  |  |   private getFieldHandler(fieldKey: number): (fieldValue: ASN1.OctetString) => void { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 81 | 348 |  |     if (fieldKey === IN_APP) { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 82 | 15 |  |       return this.parseReceiptContent.bind(this) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 83 |  |  |     } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 84 | 333 |  |     if (this.isValidReceiptFieldKey(fieldKey)) { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 85 | 159 |  |       const name = RECEIPT_FIELDS_MAP.get(fieldKey)! | 
            
                                                                                                            
                            
            
                                    
            
            
                | 86 | 159 |  |       return (fieldValue: ASN1.OctetString) => { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 87 | 159 |  |         this.addFieldToReceipt(name, this.extractStringValue(fieldValue)) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 88 |  |  |       } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 89 |  |  |     } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 90 | 174 |  |     return () => {} | 
            
                                                                                                            
                            
            
                                    
            
            
                | 91 |  |  |   } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 92 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 93 |  |  |   private isValidReceiptFieldKey(value: unknown): value is ReceiptFieldsKeyValues { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 94 | 333 |  |     return typeof value === 'number' && RECEIPT_FIELDS_MAP.has(value as ReceiptFieldsKeyValues) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 95 |  |  |   } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 96 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 97 |  |  |   private extractStringValue(field: ASN1.OctetString): string { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 98 | 159 |  |     const [fieldValue] = field.valueBlock.value | 
            
                                                                                                            
                            
            
                                    
            
            
                | 99 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 100 | 159 |  |     if (fieldValue instanceof ASN1.IA5String || fieldValue instanceof ASN1.Utf8String) { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 101 | 123 |  |       return fieldValue.valueBlock.value | 
            
                                                                                                            
                            
            
                                    
            
            
                | 102 |  |  |     } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 103 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 104 | 36 |  |     return field.toJSON().valueBlock.valueHex | 
            
                                                                                                            
                            
            
                                    
            
            
                | 105 |  |  |   } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 106 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 107 |  |  |   private addFieldToReceipt(name: ReceiptFieldsKeyNames, value: string): void { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 108 | 159 |  |     this.addToArrayFieldIfApplicable(name, value) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 109 | 159 |  |     this.parsed[name] = value | 
            
                                                                                                            
                            
            
                                    
            
            
                | 110 |  |  |   } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 111 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 112 |  |  |   private addToArrayFieldIfApplicable(name: ReceiptFieldsKeyNames, value: string): void { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 113 | 159 |  |     const arrayFields: Record<string, keyof ParsedReceipt> = { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 114 |  |  |       'IN_APP_ORIGINAL_TRANSACTION_ID': 'IN_APP_ORIGINAL_TRANSACTION_IDS', | 
            
                                                                                                            
                            
            
                                    
            
            
                | 115 |  |  |       'IN_APP_TRANSACTION_ID': 'IN_APP_TRANSACTION_IDS', | 
            
                                                                                                            
                            
            
                                    
            
            
                | 116 |  |  |     } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 117 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 118 | 159 |  |     const arrayFieldName = arrayFields[name] | 
            
                                                                                                            
                            
            
                                    
            
            
                | 119 | 159 |  |     if (arrayFieldName) { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 120 | 30 |  |       (this.parsed[arrayFieldName] as string[]).push(value) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 121 |  |  |     } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 122 |  |  |   } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 123 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 124 |  |  |   private validateParsedFields(): void { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 125 | 3 |  |     const missingFields = Array.from(RECEIPT_FIELDS_MAP.values()) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 126 | 51 |  |       .filter(fieldKey => !(fieldKey in this.parsed)) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 127 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 128 | 3 |  |     if (missingFields.length > 0) { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 129 |  |  |       throw new Error(`Missing required fields: ${missingFields.join(', ')}`) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 130 |  |  |     } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 131 |  |  |   } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 132 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 133 |  |  |   private deduplicateArrayFields(): void { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 134 | 3 |  |     this.parsed.IN_APP_ORIGINAL_TRANSACTION_IDS = this.removeDuplicates(this.parsed.IN_APP_ORIGINAL_TRANSACTION_IDS) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 135 | 3 |  |     this.parsed.IN_APP_TRANSACTION_IDS = this.removeDuplicates(this.parsed.IN_APP_TRANSACTION_IDS) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 136 |  |  |   } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 137 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 138 |  |  |   private removeDuplicates(array: string[]): string[] { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 139 | 6 |  |     return [...new Set(array)] | 
            
                                                                                                            
                            
            
                                    
            
            
                | 140 |  |  |   } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 141 |  |  | } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 142 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 143 | 1 |  | export function parseReceipt(receipt: string): ParsedReceipt { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 144 | 5 |  |   return new ReceiptParser().parseReceipt(receipt) | 
            
                                                                                                            
                                                                
            
                                    
            
            
                | 145 |  |  | } | 
            
                                                        
            
                                    
            
            
                | 146 |  |  |  |